﻿using MultiLang;
using System;
using System.IO;
using System.Reflection;
using System.Linq;
using System.Text;

namespace Rsdn.SmartApp.CommandLine
{
	internal static class UsagePrinter
	{
		public static void PrintUsage(CmdLineRules rules, TextWriter writer, PrintUsageSettings settings)
		{
			var titleExists = false;
			if (!settings.ProductNameString.IsNullOrEmpty())
			{
				writer.WriteLine(settings.ProductNameString);
				titleExists = true;
			}

			if (!settings.CopyrightString.IsNullOrEmpty())
			{
				writer.WriteLine(settings.CopyrightString);
				titleExists = true;
			}

			if (titleExists)
				writer.WriteLine();

			writer.Write(ml.ml_string(67, "Использование:"));
			if (!settings.ProgramFileName.IsNullOrEmpty())
				writer.Write(settings.ProgramFileName);
			else
			{
				var entry = Assembly.GetEntryAssembly();
				if (entry == null)
					throw new ApplicationException(ml.ml_string(68, "Не удалось получить имя файла программы. Попробуйте указать его в настройках."));
				writer.Write(entry.GetName(false).Name);
			}

			var hasCmds = rules.Commands.Length > 0;
			if (hasCmds)
			{
				writer.Write(" ");                                                //MLHIDE
				string prefix, suffix;
				switch (rules.CommandQuantifier)
				{
					case CommandQuantifier.ZeroOrOne:
						prefix = "[";                                                   //MLHIDE
						suffix = "]";                                                   //MLHIDE
						break;
					case CommandQuantifier.ZeroOrMultiple:
						prefix = "[";                                                   //MLHIDE
						suffix = "...]";                                                //MLHIDE
						break;
					case CommandQuantifier.One:
						prefix = "<";                                                   //MLHIDE
						suffix = ">";                                                   //MLHIDE
						break;
					case CommandQuantifier.OneOrMultiple:
						prefix = "<";                                                   //MLHIDE
						suffix = "...>";                                                //MLHIDE
						break;
					default:
						throw new ArgumentOutOfRangeException();
				}
				writer.Write(
					prefix
					+ (settings.RealCommandsInHeadLine
						? rules.Commands.Select(cmd => cmd.Name).Join("|")              //MLHIDE
						: "command")                                                    //MLHIDE
					+ suffix);
			}

			var hasOpts = rules.Options.Length > 0;
			string optPrefix;
			switch (settings.OptionPrefix)
			{
				case OptionPrefix.Dash:
					optPrefix = "-";                                                 //MLHIDE
					break;
				case OptionPrefix.Slash:
					optPrefix = "/";                                                 //MLHIDE
					break;
				default:
					throw new ArgumentOutOfRangeException();
			}
			if (hasOpts)
				writer.Write(
					" [{0}...]",                                                     //MLHIDE
					settings.RealOptionsInHeadLine
						? rules.Options.Select(opt => GetOptionString(opt, false, optPrefix)).Join("|") //MLHIDE
						: ml.ml_string(69, "Параметры"));

			writer.WriteLine();
			writer.WriteLine();

			if (hasCmds)
			{
				writer.WriteLine(ml.ml_string(70, "                           -КОМАНДЫ-"));
				var cmdDescs =
					rules
						.Commands
						.Select(cmd => new ItemDescriptor(cmd.Name, GetCommandDescription(cmd, rules, optPrefix)))
						.ToList();
				var maxLen = cmdDescs.Max(desc => desc.Name.Length);
				foreach (var desc in cmdDescs)
					writer.WriteLine(" {0} - {1}", desc.Name.PadRight(maxLen), desc.Description); //MLHIDE
			}

			if (hasOpts)
			{
				writer.WriteLine(ml.ml_string(71, "                            -ОПЦИИ-"));
				var optDescs =
					rules
						.Options
						.Select(opt => new ItemDescriptor(GetOptionString(opt, true, optPrefix), GetOptionDescription(opt)))
						.ToList();
				var maxLen = optDescs.Max(desc => desc.Name.Length);
				foreach (var desc in optDescs)
					writer.WriteLine(" {0} - {1}", desc.Name.PadRight(maxLen), desc.Description); //MLHIDE
			}
		}

		private static string GetOptionDescription(OptionRule opt)
		{
			var sb = new StringBuilder(opt.Description);
			if (opt.Required)
				sb.Append(ml.ml_string(72, "Обязательно."));
			if (opt.DependOnCommands.Length > 0)
				sb.AppendFormat(ml.ml_string(73, "Действует для команды: {0}.") , opt.DependOnCommands.Join(", ")); //MLHIDE
			return sb.ToString();
		}

		private static string GetOptionString(OptionRule opt, bool full, string optPrefix)
		{
			switch (opt.Type)
			{
				case OptionType.Valueless:
					return optPrefix + opt.Name;
				case OptionType.Bool:
					return optPrefix + opt.Name + (full ? "[+|-]" : "+");            //MLHIDE
				case OptionType.Value:
					return optPrefix + opt.Name + (full ? "=<value>" : "=");         //MLHIDE
				default:
					throw new ArgumentOutOfRangeException();
			}
		}

		private static string GetCommandDescription(CommandRule cmd, CmdLineRules rules, string optPrefix)
		{
			var sb = new StringBuilder(cmd.Description);
			var cmdOpts =
				rules
					.Options
					.Where(opt => opt.DependOnCommands.Contains(cmd.Name))
					.ToArray();
			var reqOpts =
				cmdOpts
					.Where(opt => opt.Required)
					.Select(opt => GetOptionString(opt, false, optPrefix))
					.Join(", ");                                                     //MLHIDE
			if (reqOpts.Length > 0)
				sb.AppendFormat(ml.ml_string(74, "Требуется: {0}."), reqOpts);
			var optOpts =
				cmdOpts
					.Where(opt => !opt.Required)
					.Select(opt => GetOptionString(opt, false, optPrefix))
					.Join(", ");                                                     //MLHIDE
			if (optOpts.Length > 0)
				sb.AppendFormat(ml.ml_string(75, "Необязательно: {0}."), optOpts);
			return sb.ToString();
		}

		#region ItemDescriptor class
		private class ItemDescriptor
		{
			private readonly string _name;
			private readonly string _description;

			public ItemDescriptor(string name, string description)
			{
				_name = name;
				_description = description;
			}

			public string Name
			{
				get { return _name; }
			}

			public string Description
			{
				get { return _description; }
			}
		}
		#endregion
	}
}